package com.miracle.shiro.controller;


import com.miracle.shiro.config.BaseErrorController;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;
import java.util.UUID;

/**
 * @Author: rupert-tears
 * @Date: Created in 10:52 2021/7/16
 * @Description: 优秀的人都在孤独的翻山越岭！
 * 将这个类交给spring管理 注解生效
 *
 * 如果需要访问这个方法必须要求用户拥有指定的权限才可以访问
 * 属性
 *      value 取值为String的数组，可以同时指定一个或者多个权限名
 *      logical 取值是枚举类型 Logical.AND或Logical.OR，AND必须包含value所指定的所有权限
 *      OR只要包含value所指定的某个一个权限即可访问
 * @RequiresRoles(value={""}),logical=logical.AND
 * 如果需要访问这个方法必须要求用户拥有指定的角色才可以访问
 * 属性
 *      value 取值为 String 的数组，可以同事指定一个或者多个角色名称
 *      logical 取值是枚举类型 Logical.AND 或 Logical.OR AND必须包含 value 所指定的所有角色
 *      OR 只要包含 value 所指定的某一个角色即可访问
 *
 *  如果当前类中抛出和Shiro相关的异常 就会被这个父接口中的声明式异常处理拦截
 *
 */
@Controller
public class TestController implements BaseErrorController {
    /**
     * 拦截所有请求
     *
     * @return 跳转登录页面
     */
    @RequestMapping("/")
    public String index() {
        return "index";
    }

    /**
     * 登录跳转
     * 参数接受不到的原因 前端页面中，必须使用name属性 与 该处的属性对应
     * id 是不行滴！
     *
     * @return 登录成功页面
     */
    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        // 获取当前用户对象 （当前操作对象，这个操作可以是一个用户，也可以是一个第三方应用）
        Subject subject = SecurityUtils.getSubject();
        // 定义一个存放用户输入的账号和密码的身份令牌对象
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        // 判断当前的操作用户是否完成了用户登录认证（用户是否登录）进入if标识用户已经完成登录
        if (subject.isAuthenticated()){
            /*
             * 如果要解决用户再次登录，随便输入用户名密码都可以进入，有三种解决方案
             * 1.不写这个if，但是会造成重复的发送用户登录请求，增加服务器负荷
             * 2.调用Subject中的Logout()方法，用于退出当前的登录状态，会清空会话中所有数据
             * 3.在页面中便也一个安全退出请求，用于用户主动退出登录状态
             */
            return "redirect:/success";
        }
        // 程序到这里表示用户没有登录，需要完成用户登录
        try{
            // 调用用户登录方法，后台Shiro会自动执行MyRealm中的用户认证方法
            // 如果登录成功 login方法将正常结束 否则将抛出异常 我们需要根据异常信息 为用户进行友好提示
            subject.login(token);
        }catch (UnknownAccountException e){
            model.addAttribute("errMsg","登录失败，账号不存在！");
            return "index";
        }catch (LockedAccountException e){
            model.addAttribute("errMsg","登录失败，账号已被冻结！");
            return "index";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("errMsg","登录失败，密码错误！");
            return "index";
        }catch (AuthenticationException e) {
            model.addAttribute("errMsg", "登录失败！");
            return "index";
        }
        // 重定向跳转 请求新的路由
        // 登录成功不需要 记录Session会话 Shiro会将这些用户状态信息记录到会话中（其实是subject中）
         return "redirect:/success";
    }

    /**
     * 页面跳转 接收上述请求路由
     *
     * @return 成功登录页面
     */
    @RequestMapping("/success")
    public String success() {
        return "success";
    }

    /**
     * 页面跳转
     *
     * @return 问题页面
     */
    @RequestMapping("/noperms")
    public String noperms() {
        return "noperms";
    }

    /**
     * 管理员页面跳转
     *
     * @return 字符输出页面
     */
    @RequestMapping("/admin/test")
    public @ResponseBody
    String adminTest() {
        return "这是AdminTest页面";
    }

    /**
     * 用户页面跳转
     *
     * @return 字符输出页面
     */
    @RequestMapping("/user/test")
    public @ResponseBody
    String userTest() {
        return "这是UserTest页面";
    }

    /**
     * 获取随机盐
     * @return 随机盐
     */
    @RequestMapping("/getRandomSalt")
    public @ResponseBody String getRandomSalt(HttpSession session){
        String uuid = UUID.randomUUID().toString();
        // 使用spring中提供的工具类对随机的UUID使用MD5加密
        String randomSalt = DigestUtils.md5DigestAsHex(uuid.getBytes());
        // 同步加密 将随机盐放入Session中 前后端共享
        session.setAttribute("randomSalt",randomSalt);
        return randomSalt;
    }

    /**
     * 添加管理员添加方法
     * @return 页面输出
     */
    @RequestMapping("admin/add")
    public @ResponseBody String adminAdd(){
        return "这是Admin的Add页面";
    }

    @RequiresRoles(value = "admin")
    @RequiresPermissions(value = "admin:add")
    @RequestMapping("/admin/test2")
    public @ResponseBody String adminTest2(){
        return "这是AdminTest";
    }

}
